package com.springboot.frame.service.impl;

import com.springboot.frame.Condition;
import com.springboot.frame.enums.BatchType;
import com.springboot.frame.mapper.BaseMapper;
import com.springboot.frame.service.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.ibatis.binding.MapperProxy;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * 公共service实现
 *
 * @author liheng
 */
@Slf4j
public class BaseServiceImpl<T extends Serializable, PK extends Serializable> implements
        BaseService<T, PK> {

  private static final int DEFAULT_FLUSH_SIZE = 1000;

  @Autowired
  private BaseMapper<T, PK> baseMapper;

  @Autowired
  private SqlSessionFactory sqlSessionFactory;

  private String nameSpace;

  @PostConstruct
  public void init() {
    MapperProxy mapperProxy = (MapperProxy) SystemMetaObject.forObject(baseMapper).getValue("h");
    Class<?> mapperInterface = (Class<?>) SystemMetaObject.forObject(mapperProxy)
        .getValue("mapperInterface");
    nameSpace = mapperInterface.getName();
  }

  @Override
  @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
  public T selectById(PK id) {
    return baseMapper.selectById(id);
  }

  @Override
  @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
  public T selectOnly(Condition<T> condition) {
    return baseMapper.selectOnly(condition);
  }

  @Override
  @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
  public List<T> selectBatchIds(Collection<PK> ids) {
    return baseMapper.selectBatchIds(ids);
  }

  @Override
  @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
  public PK selectCount(Condition<T> condition) {
    return baseMapper.selectCount(condition);
  }

  @Override
  @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
  public List<T> selectList(Condition<T> condition) {
    return baseMapper.selectList(condition);
  }


  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean deleteById(PK id) {
    return baseMapper.deleteById(id);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean delete(Condition<T> condition) {
    return condition != null && baseMapper.delete(condition);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean deleteBatchIds(Collection<PK> ids) {
    return baseMapper.deleteBatchIds(ids);
  }

  /**
   * 清空表
   *
   * @return
   */
  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean truncateTable() {
    return baseMapper.truncateTable();
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean insert(T t) {
    return t != null && baseMapper.insert(t);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean insertAllColumns(T t) {
    return t != null && baseMapper.insertAllColumns(t);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean updateById(T t) {
    return t != null && baseMapper.updateById(t);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean updateAllColumnsById(T t) {
    return t != null && baseMapper.updateAllColumnsById(t);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean update(Condition<T> condition) {
    return condition != null && baseMapper.update(condition);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean updateAllColumns(Condition<T> condition) {
    return condition != null && baseMapper.updateAllColumns(condition);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean saveOrUpdate(T t) {
    return t != null && baseMapper.saveOrUpdate(t);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean saveOrUpdateAllColumns(T t) {
    return t != null && baseMapper.saveOrUpdateAllColumns(t);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean batchInsert(Collection<T> data) {
    return executeBatch(data, BatchType.INSERT);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean batchInsertAllColumns(Collection<T> data) {
    return executeBatch(data, BatchType.INSERT_ALL_COLUMNS);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean batchUpdateById(Collection<T> data) {
    return executeBatch(data, BatchType.UPDATE_BY_ID);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean batchUpdateAllColumnsById(Collection<T> data) {
    return executeBatch(data, BatchType.UPDATE_ALL_COLUMNS_BY_ID);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean batchSaveOrUpdate(Collection<T> data) {
    return executeBatch(data, BatchType.SAVE_OR_UPDATE);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean batchSaveOrUpdateAllColumns(Collection<T> data) {
    return executeBatch(data, BatchType.SAVE_OR_UPDATE_ALL_COLUMNS);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean executeBatch(Collection<T> data, BatchType batchType) {
    return executeBatch(data, batchType, DEFAULT_FLUSH_SIZE);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public boolean executeBatch(Collection<T> data, BatchType batchType, int batchSize) {
    if (checkArgument(data, batchType)) {
      return false;
    }
    int flushSize = batchSize <= 1 ? DEFAULT_FLUSH_SIZE : batchSize;
    Iterator<T> iterator = data.iterator();
    String statement = nameSpace + batchType;
    try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) {
      int index = 0;
      while (iterator.hasNext()) {
        sqlSession.update(statement, iterator.next());
        if ((index + 1) % flushSize == 0) {
          sqlSession.flushStatements();
        }
      }
      sqlSession.flushStatements();
    }
    return true;
  }

  private boolean checkArgument(Collection<T> data, BatchType batchType) {
    if (batchType == null) {
      throw new IllegalArgumentException("批量操作必须选定枚举方法");
    }
    return (CollectionUtils.isEmpty(data)) ? true : false;
  }

}
